﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;

namespace JMacia.Gaming.d20.Core.Traits
{
    /// <summary>
    /// A representation of a modifier that can be applied, directly or recursively, to a Trait
    /// </summary>
    public class Modifier
    {
        #region Private Members
        private ModifierType _type;
        private int _value;
        private Modifiers _metaModifiers = new Modifiers();
        private Modifier _parentModifier;
        private Trait _traitModified;
        private Effect _source;
        private bool _selfStacking;
        private Predicate<Modifier> _conditionEvaluator;
        private List<string> _conditions;
        #endregion

        #region Constructors
        /// <summary>
        /// Creates a new instance of a Modifier
        /// </summary>
        public Modifier() : this(0, SystemModifierType.Untyped, null, null) { }

        /// <summary>
        /// Creates a new instance of a Modifier and initializes the value
        /// </summary>
        /// <param name="value">The initial value of the modifier</param>
        public Modifier(int value) : this(value, SystemModifierType.Untyped, null, null) { }
        
        /// <summary>
        /// Creates a new instance of a Modifier and initializes the value and type
        /// </summary>
        /// <param name="value">The initial value of the modifier</param>
        /// <param name="type">The type of modifier</param>
        public Modifier(int value, ModifierType type) : this(value, type, null, null) { }

        /// <summary>
        /// Creates a new instance of a Modifier and initializes the value and type
        /// </summary>
        /// <param name="value">The initial value of the modifier</param>
        /// <param name="type">The type of modifier</param>
        /// <param name="conditionEvaluator">A predicate that will determine whether the modifier applies</param>
        public Modifier(int value, ModifierType type, Predicate<Modifier> conditionEvaluator) : this(value, type, null, conditionEvaluator) { }
        
        /// <summary>
        /// Creates a new instance of a Modifier and initializes the value and type
        /// </summary>
        /// <param name="value">The initial value of the modifier</param>
        /// <param name="type">The type of modifier</param>
        public Modifier(int value, string type) : this(value, new ModifierType(type), null, null) { }
        
        /// <summary>
        /// Creates a new instance of a Modifier and initializes the value, 
        /// and applies the new modifier as a metamodifier to another modifier
        /// </summary>
        /// <param name="value">The initial value of the modifier</param>
        /// <param name="parent">The modifier to which to apply this instance as a metamodifier</param>
        public Modifier(int value, Modifier parent) : this(value, new ModifierType(SystemModifierType.Enhancement), parent) { }
        
        /// <summary>
        /// Creates a new instance of a Modifier and initializes the value and type
        /// </summary>
        /// <param name="value">The initial value of the modifier</param>
        /// <param name="type">The type of modifier</param>
        /// <param name="parent">The modifier to which to apply this instance as a metamodifier</param>
        public Modifier(int value, ModifierType type, Modifier parent) : this(value, type, parent, null) { }

        /// <summary>
        /// Creates a new instance of a conditional Modifier and initializes the value and type
        /// </summary>
        /// <param name="value">The initial value of the modifier</param>
        /// <param name="type">The type of modifier</param>
        /// <param name="parent">The modifier to which to apply this instance as a metamodifier</param>
        /// <param name="conditionEvaluator">A predicate that will determine whether the modifier applies</param>
        public Modifier(int value, ModifierType type, Modifier parent, Predicate<Modifier> conditionEvaluator)
        {
            _value = value;
            _type = type;
            _conditionEvaluator = conditionEvaluator;
            if (parent != null)
            {
                _parentModifier = parent;
                parent._metaModifiers.Add(this);
            }
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// The type of bonus the modifier provides
        /// </summary>
        public ModifierType Type
        {
            get
            {
                return _type;
            }
            set
            {
                _type = value;
            }
        }
        
        /// <summary>
        /// The base value of the modifier not including any metamodifiers (e.g., enhancement bonus to natural armor)
        /// </summary>
        public virtual int BaseValue
        {
            get
            {
                if (_conditionEvaluator != null && !_conditionEvaluator(this)) { return 0; }
                return _value;
            }
            set
            {
                _value = value;
            }
        }
        
        /// <summary>
        /// The total value of the modifier, including all metamodifiers (e.g., enhancement bonus to natural armor)
        /// </summary>
        public virtual int TotalValue
        {
            get
            {
                if (_conditionEvaluator != null && !_conditionEvaluator(this)) { return 0; }
                // use BaseValue instead of _value in case of overrides
                return BaseValue + _metaModifiers.TotalValue;
            }
        }
        
        /// <summary>
        /// The modifier that this modifier applies to as a metamodifier
        /// </summary>
        public Modifier Parent
        {
            get
            {
                return _parentModifier;
            }
            set
            {
                if (_parentModifier != null)
                {
                    _parentModifier._metaModifiers.Remove(this);    // remove ourselves from the metamodifier list of our old parent
                }
                _parentModifier = value;
                if (_parentModifier != null && !_parentModifier._metaModifiers.Contains(this, new ReferenceEqualityComparer()))
                {
                    _parentModifier._metaModifiers.Add(this);                 // add ourselves to the metamodifier list of the new parent
                }
            }
        }
        
        /// <summary>
        /// A copy of the list of modifiers that apply to this modifier instead of directly to a trait (e.g., enhancement bonus to natural armor)
        /// </summary>
        /// <remarks>Changes to this property are not saved.  To add or remove metamodifiers, change their parent modifier.</remarks>
        public Modifiers MetaModifiers
        {
            get
            {
                return new Modifiers(this._metaModifiers);
            }
        }
        
        /// <summary>
        /// The trait to which this modifier applies, whether directly or recursively.
        /// </summary>
        public Trait TraitModified
        {
            get
            {
                if (_parentModifier == null)
                {
                    return _traitModified;
                }
                return _parentModifier.TraitModified;
            }
            internal set
            {
                _traitModified = value;
            }
        }

        /// <summary>
        /// The effect that bestows this modifier
        /// </summary>
        public Effect Source
        {
            get { return _source; }
            internal set { _source = value; }
        }

        /// <summary>
        /// Whether or not another instance of this modifier's effect will stack with this instance.
        /// </summary>
        public bool SelfStacking
        {
            get { return _selfStacking; }
            set { _selfStacking = value; }
        }

        /// <summary>
        /// A set of conditions under which the modifier applies
        /// </summary>
        public List<string> Conditions
        {
            get { return _conditions; }
            set { _conditions = value; }
        }
        #endregion

        #region Public Instance Methods
        /// <summary>
        /// Creates a new Modifier with the same attributes as this modifier.
        /// All metamodifiers are also duplicated, but the source Effect, parent Modifier,
        /// and Trait modified are left for the user to define.
        /// </summary>
        /// <returns></returns>
        public virtual Modifier Clone()
        {
            Modifier m = this.MemberwiseClone() as Modifier;
            if (m == null) { return null; }  // should not be possible, let's move on

            m._metaModifiers = m._metaModifiers.Clone();
            m._parentModifier = null;
            m._source = null;
            m._traitModified = null;

            return m;
        }

        /// <summary>
        /// Determines by the standard stacking rules whether or not this modifier stacks with another given modifier.
        /// </summary>
        /// <param name="otherMod">Another modifier with which to determine stacking</param>
        /// <returns>True if the modifiers stack, false if they overlap.</returns>
        public virtual bool StacksWith(Modifier otherMod)
        {
            if (!object.ReferenceEquals(this.TraitModified, otherMod.TraitModified))
            {
                return true;
            }// if the modifiers apply to different traits, they "stack", in that they don't interfere with one another

            if (this._type != otherMod._type)
            {
                return true;
            }// if the modifiers are of different types, they always stack

            if (this._type == SystemModifierType.Ability ||
                this._type == SystemModifierType.Dodge ||
                this._type == SystemModifierType.Circumstance ||
                this._type == SystemModifierType.Untyped)
            {
                if (this._type.IsStacking.GetValueOrDefault(true) &&
                    this._type.IsStacking.GetValueOrDefault(true))
                {
                    return this._source != otherMod._source;
                }
                return false;
            }// Ability, Dodge, Circumstance, and untyped modifiers always stack unless they explicitly say they don't, or if they come from the same source

            if (this._type.IsStacking.GetValueOrDefault(false) ||
                otherMod._type.IsStacking.GetValueOrDefault(false))
            {
                return true;
            }// if the one modifier's type is defined as explicitly stacking, then they stack

            // if they are metamodifiers, they stack if they apply to different modifiers (e.g., enhancement to natural armor "stacks" with enhancement to armor)
            return !object.ReferenceEquals(this._parentModifier, otherMod._parentModifier);
        }
        
        /// <summary>
        /// Determines whether the current modifier is equal to a given object
        /// </summary>
        /// <param name="obj">The object to which to compare the current modifier</param>
        /// <returns>true if the object is a Modifier with the same value, type and metemodifiers,
        /// false otherwise</returns>
        public override bool Equals(object obj)
        {
            Modifier m = obj as Modifier;
            return this == m;  // == operator checks for null
        }

        /// <summary>
        /// Provides a hash code for the modifier
        /// </summary>
        /// <returns>A hash code integer</returns>
        public override int GetHashCode()
        {
            return _type.GetHashCode() ^ _value.GetHashCode() ^ _metaModifiers.GetHashCode();
        }

        /// <summary>
        /// Provides a string representation of the modifier
        /// </summary>
        /// <returns>A string that indicates the bonus or penalty amount and type</returns>
        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();
            int total = TotalValue;
            if (total >= 0)
            {
                builder.Append('+');
            }
            builder.Append(total);
            builder.Append(' ');
            builder.Append(_type);

            return builder.ToString();
        }
        #endregion

        #region Operators
        /// <summary>
        /// Compares two modifiers for equality
        /// </summary>
        /// <param name="m">One modifier</param>
        /// <param name="n">Another modifier</param>
        /// <returns>true if both modifiers have the same value, type and metamodifiers,
        /// false otherwise</returns>
        public static bool operator ==(Modifier m, Modifier n)
        {
            if (object.ReferenceEquals(m, null))
            {
                return object.ReferenceEquals(n, null);
            }
            if (object.ReferenceEquals(n, null)) { return false; }
            return (m._type == n._type) && (m._value == n._value) && (m._metaModifiers == n._metaModifiers) && (m._source == n._source);
        }

        /// <summary>
        /// Compares two modifiers for inequality
        /// </summary>
        /// <param name="m">One modifier</param>
        /// <param name="n">Another modifier</param>
        /// <returns>false if both modifiers have the same value, type and metamodifiers,
        /// true otherwise</returns>
        public static bool operator !=(Modifier m, Modifier n)
        {
            return !(m == n);
        }
        #endregion

        /// <summary>
        /// An equality comparer that can be used to override the standard equality criteria of a Modifier with that of reference equality
        /// </summary>
        public class ReferenceEqualityComparer : IEqualityComparer<Modifier>
        {
            #region IEqualityComparer<Modifier> Members
            /// <summary>
            /// Compares two Modifiers for reference equality
            /// </summary>
            /// <param name="x">One modifier</param>
            /// <param name="y">Another modifier</param>
            /// <returns>True if the two parameters refer to the same Modifier instance,
            /// false otherwise</returns>
            public bool Equals(Modifier x, Modifier y)
            {
                return object.ReferenceEquals(x, y);
            }

            /// <summary>
            /// Gives a hash code for the specified Modifier
            /// </summary>
            /// <param name="obj">The modifier for which to get the hash code</param>
            /// <returns>A hash code for the modifier</returns>
            public int GetHashCode(Modifier obj)
            {
                return obj.GetHashCode();
            }
            #endregion
        }
    }

    /// <summary>
    /// An extension of a generic list of Modifier objects with various utility methods dealing with stacking and overlapping modifiers
    /// </summary>
    public class Modifiers : ObservableCollection<Modifier>
    {
        #region Private Members
        private static Modifier.ReferenceEqualityComparer _refEqualsComparer = new Modifier.ReferenceEqualityComparer();
        #endregion

        #region Constructors
        /// <summary>
        /// Creates a new, empty instance of the Modifiers collection
        /// </summary>
        public Modifiers() : base() { }

        /// <summary>
        /// Creates a Modifiers collection from any number of Modifier parameters
        /// </summary>
        /// <param name="mods">Modifier objects to be contained in the collection</param>
        public Modifiers(params Modifier[] mods) : base(mods) { }

        /// <summary>
        /// Creates a Modifiers collection from an enumerable collection of Modifier objects
        /// </summary>
        /// <param name="collection">Collection whose elements will be copied to the new collection</param>
        public Modifiers(IEnumerable<Modifier> collection) : base(collection) { }
        #endregion

        #region Public Properties
        /// <summary>
        /// The subset of modifiers in the collection that have the greatest total value of any stacking subset
        /// </summary>
        public Modifiers AppliedModifiers
        {
            get
            {
                return new Modifiers(this.Except(IgnoredModifiers, _refEqualsComparer));
            }
        }

        /// <summary>
        /// The subset of modifiers that fail to stack with another modifier in the collection
        /// and have a lesser value than that modifier
        /// </summary>
        public Modifiers IgnoredModifiers
        {
            get
            {
                Modifiers ignored = new Modifiers();
                foreach (Modifier m in this)
                {
                    foreach (Modifier n in this)
                    {
                        if (object.ReferenceEquals(m, n) ||
                            m.StacksWith(n) ||
                            ignored.Contains(m, _refEqualsComparer) ||
                            ignored.Contains(n, _refEqualsComparer))
                        {
                            continue;
                        }
                        if (m.TotalValue >= n.TotalValue)
                        {
                            ignored.Add(n);
                        }
                        else
                        {
                            ignored.Add(m);
                        }
                    }
                }
                return ignored;
            }
        }

        /// <summary>
        /// The total value of the highest-valued stacking subset in the collection.
        /// </summary>
        public int TotalValue
        {
            get
            {
                int total = 0;
                foreach (Modifier m in AppliedModifiers)
                {
                    total += m.TotalValue;
                }
                return total;
            }
        }
        #endregion

        #region Operators
        /// <summary>
        /// Compares two sets of modifiers for equality
        /// </summary>
        /// <param name="one">One set of modidfiers</param>
        /// <param name="two">Another set of modifiers</param>
        /// <returns>true if both sets contain all equal elements
        /// false otherwise</returns>
        public static bool operator ==(Modifiers one, Modifiers two)
        {
            if (object.ReferenceEquals(one, null))
            {
                return object.ReferenceEquals(two, null);
            }
            int count = one.Count;
            return (count == two.Count) && (one.Intersect(two).Count() == count);
        }

        /// <summary>
        /// Compares two sets of modifiers for inequality
        /// </summary>
        /// <param name="one">One set of modifiers</param>
        /// <param name="two">Another set of modifiers</param>
        /// <returns>false if both sets contain all the same elements
        /// true otherwise</returns>
        public static bool operator !=(Modifiers one, Modifiers two)
        {
            return !(one == two);
        }
        #endregion

        #region Public Instance Methods
        public Modifiers Clone()
        {
            Modifiers m = new Modifiers();

            foreach (Modifier mod in this)
            {
                m.Add(mod.Clone());
            }

            return m;
        }

        /// <summary>
        /// Determines whether the current set of modifiers is equal to a given object
        /// </summary>
        /// <param name="obj">The object to which to compare the current set of modifiers</param>
        /// <returns>true if the object is a Modifiers collection with elements equal to all of the elements in this collection,
        /// false otherwise</returns>
        public override bool Equals(object obj)
        {
            Modifiers mods = obj as Modifiers;
            if (mods == null)
            {
                return false;
            }
            return this == mods;
        }

        /// <summary>
        /// Provides a hash code for the modifiers
        /// </summary>
        /// <returns>A hash code integer</returns>
        public override int GetHashCode()
        {
            int hash = 0;
            foreach (Modifier m in this)
            {
                hash ^= m.GetHashCode();
            }
            return hash;
        }
        #endregion
    }

    /// <summary>
    /// A generalized modifier type that allows for both enumerated system values and custom, user-entered values
    /// </summary>
    public struct ModifierType
    {
        #region Private Members
        private string _type;
        private SystemModifierType _systemType;
        private bool? _stacking;
        #endregion

        #region Public Static Readonly Member
        /// <summary>
        /// An array of ModifierTypes representing all the system types
        /// </summary>
        public static readonly ModifierType[] AllSystemTypes
            = Enum.GetValues(typeof(SystemModifierType)).Cast<ModifierType>().ToArray();
        #endregion

        #region Constructors
        /// <summary>
        /// Creates a new ModifierType from a string
        /// </summary>
        /// <param name="type">The modifier type, which may be non-System, to represent</param>
        public ModifierType(string type)
        {
            _type = string.Empty;
            _systemType = SystemModifierType.Untyped;
            _stacking = null;
            Parse(type);
        }

        /// <summary>
        /// Creates a new ModifierType from a system modifier type
        /// </summary>
        /// <param name="type">The system modifier type to represent</param>
        public ModifierType(SystemModifierType type)
        {
            _systemType = type;
            _type = _systemType.ToString();
            _stacking = null;
        }
        #endregion
        
        #region Public Properties
        /// <summary>
        /// The modifier type as a string
        /// </summary>
        public string Type
        {
            get
            {
                return _type;
            }
            set
            {
                Parse(value);
            }

        }

        /// <summary>
        /// A reduction of the type to a system type
        /// </summary>
        /// <remarks>A non-system type show as Untyped</remarks>
        public SystemModifierType SystemType
        {
            get
            {
                return _systemType;
            }
            set
            {
                _systemType = value;
                _type = _systemType.ToString();
            }
        }

        /// <summary>
        /// True if the type is a system type, false otherwise
        /// </summary>
        public bool IsSystemType
        {
            get
            {
                return _type == _systemType.ToString();
            }
        }

        /// <summary>
        /// Whether this modifier type should stack with other modifiers of its type.
        /// A null value indicates that it uses the default for its type, which is false
        /// for most system types and all user-defined types.
        /// </summary>
        public bool? IsStacking
        {
            get { return _stacking; }
            set { _stacking = value; }
        }
        #endregion

        #region Public Instance Methods
        /// <summary>
        /// Determines whether this modifier type is equal to a given object
        /// </summary>
        /// <param name="obj">The object to which to compare</param>
        /// <returns>True if the object is equal to this ModifierType, false otherwise</returns>
        public override bool Equals(object obj)
        {
            ModifierType? mt = obj as ModifierType?;
            if (mt == null)
            {
                return false;
            }
            return mt == this;
        }

        /// <summary>
        /// Gives a hash code for use in a hash table
        /// </summary>
        /// <returns>A valid hash code for the modifier type</returns>
        public override int GetHashCode()
        {
            return _type.GetHashCode();
        }

        /// <summary>
        /// Represents the modifier type as a string
        /// </summary>
        /// <returns>The modifier type name</returns>
        public override string ToString()
        {
            return _type;
        }
        #endregion

        #region Private Utility Methods
        /// <summary>
        /// Parses a given string and if it represents an enumerated system modifier type,
        /// sets the system type of this instance to that type.  Otherwise, set our system type to Untyped
        /// </summary>
        /// <param name="type">A string from which to determine our system type</param>
        private void Parse(string type)
        {
            _type = type.Replace(" ", string.Empty);
            if (Enum.IsDefined(typeof(SystemModifierType), _type))
            {
                _systemType = (SystemModifierType)Enum.Parse(typeof(SystemModifierType), _type, true);
            }
            else
            {
                _systemType = SystemModifierType.Untyped;
            }
        }
        #endregion
            
        #region Operators
        /// <summary>
        /// Compares two modifier types for equality
        /// </summary>
        /// <param name="modA">One modifier type</param>
        /// <param name="modB">Another modifier type</param>
        /// <returns>true if these modifier types have the same string representation (case insensitive), false otherwise</returns>
        public static bool operator ==(ModifierType modA, ModifierType modB)
        {
            return string.Equals(modA._type, modB._type, StringComparison.CurrentCultureIgnoreCase);
        }

        /// <summary>
        /// Compares two modifier types for inequality
        /// </summary>
        /// <param name="modA">One modifier type</param>
        /// <param name="modB">Another modifier type</param>
        /// <returns>false if these modifier types have the same string representation (case insensitive), true otherwise</returns>
        public static bool operator !=(ModifierType modA, ModifierType modB)
        {
            return !(modA == modB);
        }
        
        /// <summary>
        /// Implicitly converts an enumerated system modifier type to a general modifier type
        /// </summary>
        /// <param name="s">The system type to generalize</param>
        /// <returns>A general modifier type</returns>
        public static implicit operator ModifierType(SystemModifierType s)
        {
            return new ModifierType(s);
        }

        /// <summary>
        /// Explicitly casts a modifier type to its system type. Custom modifiers convert to Untyped.
        /// </summary>
        /// <param name="m">the ModifierType to convert</param>
        /// <returns>the system modifier type</returns>
        public static explicit operator SystemModifierType(ModifierType m)
        {
            return m._systemType;
        }
        #endregion
    }

    /// <summary>
    /// An enumeration of Modifier types contained in the d20 SRD
    /// </summary>
    public enum SystemModifierType
    {
        Untyped,
        Ability,
        Alchemical,
        Armor,
        Circumstance,
        Competence,
        Deflection,
        Dodge,
        Enhancement,
        Insight,
        Luck,
        Morale,
        NaturalArmor,
        Profane,
        Racial,
        Resistance,
        Sacred,
        Shield,
        Size,
        Synergy,
    }
}
